#include "perpective.h"
#include "../core/geometry.h"
#include "../core/sampling.h"
#include "../core/film.h"

PerspectiveCamera::PerspectiveCamera(const Transform &w2c, float cNear, float cFar, 
									 float sOpen, float sClose, float lensr, float focald, float fov, Reference<Film> fm) : 
										ProjectiveCamera(w2c, Perspective(fov, cNear, cFar),
															cNear,cFar,sOpen,sClose,lensr,focald,fm) { 
	dxCamera = rasterToCamera(Point3(1,0,0)) - rasterToCamera(Point3(0,0,0));
	dyCamera = rasterToCamera(Point3(0,1,0)) - rasterToCamera(Point3(0,0,0));			
	fovAngle = fov;
}

float PerspectiveCamera::GenerateRay(const Point2 &imageSample, const Point2 &lensSample, float timeSample, Ray *ray) const {
	
	Point3 Pras(imageSample.x, imageSample.y, 0);
	Point3 Pcamera;
	rasterToCamera(Pras, &Pcamera);

	*ray = Ray(Point3(0,0,0),Normalize(Vector3(Pcamera)));

	// Modify ray for depth of field
	if(lensRadius > 0.) {

		// Sample point on lens
		Point2 lens = ConcentricSamplingDisk(lensSample) * lensRadius;

		// Get focus point in focus plane
		float ft = focalDistance / ray->d.z;
		Point3 Pfocus = (*ray)(ft);

		// Perturb the origin of the ray
		ray->o.x += lens.x;
		ray->o.y += lens.y;

		// Modify the direction of the ray
		ray->d = Normalize(Pfocus - ray->o);
	}

	ray->time = Lerp(timeSample, shutterOpen, shutterClose);

	// Calculate mint, maxt for ray
	float invZ = 1.f / ray->d.z;

	ray->mint = clipNear * invZ;
	ray->maxt = clipFar * invZ;
	
	/*ray->mint = 0.;
	ray->maxt = (clipFar - clipNear) * invZ;*/

	cameraToWorld(*ray, ray);

	return 1.f;
}

float PerspectiveCamera::GenerateDifferentialRay(const Point2 &imageSample, const Point2 &lensSample, float timeSample, RayDifferential *ray) const {
	
	Point3 Pras(imageSample.x, imageSample.y, 0);
	Point3 Pcamera;
	rasterToCamera(Pras, &Pcamera);

	*ray = RayDifferential(Point3(0,0,0),Normalize(Vector3(Pcamera)));

	// Modify ray for depth of field
	if(lensRadius > 0.) {

		// Sample point on lens
		Point2 lens = ConcentricSamplingDisk(lensSample) * lensRadius;

		// Get focus point in focus plane
		float ft = focalDistance / ray->d.z;
		Point3 Pfocus = (*ray)(ft);

		// Perturb the origin of the ray
		ray->o.x += lens.x;
		ray->o.y += lens.y;

		// Modify the direction of the ray
		ray->d = Normalize(Pfocus - ray->o);
	}
	ray->time = Lerp(timeSample, shutterOpen, shutterClose);

	// Calculate mint, maxt for ray
	float invZ = 1.f / ray->d.z;

	ray->mint = clipNear * invZ;
	ray->maxt = clipFar * invZ;

	/*ray->mint = 0.;
	ray->maxt = (clipFar - clipNear) * invZ;*/

	// Compute rxOrigin, ryOrigin, rxDirection, ryDirection
	ray->rxOrigin = ray->ryOrigin = ray->o;
	ray->rxDirection = Normalize(Vector3(Pcamera) + dxCamera);
	ray->ryDirection = Normalize(Vector3(Pcamera) + dyCamera);

	ray->hasDifferentials = true;

	cameraToWorld(*ray, ray);
	
	return 1.f;
}

bool PerspectiveCamera::NeedLensSample() const {
	return lensRadius > 0.0;
}

std::string PerspectiveCamera::ToString() const {
	std::ostringstream oss;
	oss << "PerspectiveCamera[" << std::endl
		<< "  aspect = " << (float)film->GetSize().x/film->GetSize().y<< "," << std::endl
		<< "  fov = " << fovAngle << "," << std::endl
		<< "  nearClip = " << clipNear << "," << std::endl
		<< "  farClip = " << clipFar << "," << std::endl
		<< "  shutterOpen = " << shutterOpen << "," << std::endl
		<< "  shutterClose = " << shutterClose << "," << std::endl
		<< "  lensRadius = " << lensRadius << "," << std::endl
		<< "  focalDistance = " << focalDistance << "," << std::endl
		<< "  cameraToWorld = " << (cameraToWorld.ToString()) << "," << std::endl
		<< "  cameraToScreen = " << (cameraToScreen.ToString()) << "," << std::endl
		<< "  rasterToCamera = " << (rasterToCamera.ToString()) << std::endl
		<< "]";

	return oss.str();
}